#pragma once

#include "Error.h"
#include "Log.h"
#include "Socket.h"
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <functional>
#include <sys/select.h>
#include <unistd.h>

namespace select_server {
    class SelectServer {
    public:
        constexpr static int defaultPort = 18989; // 服务器的默认端口号
        constexpr static int fdsArraySize = sizeof(fd_set) * 8; // fdsArray的大小，因为fd_set是位图结构(结构体里套int类型数组)，所以最后的大小是fd_set的大小 * 8
        constexpr static int defaultFd = -1; // fd的默认数值
        constexpr static int receiveBufferSize = 1024; // 接收数据的缓存区大小

    private:
        using func_t = std::function<std::string (const std::string&)>;

    private:
        uint16_t _port;
        int _listen_sockfd;
        func_t _handle_method; // 对recv上来的信息的处理方法
        int* _fds_array; // 存储需要select帮忙等待的有效sockfd

    public:
        //@param port defaultPort = 18989
        SelectServer(const func_t& handle_method, const uint16_t& port = defaultPort)
            : _port(port)
            , _listen_sockfd(defaultFd)
            , _handle_method(handle_method)
            , _fds_array(new int[fdsArraySize])
        {
            // 将_fds_array初始化
            for (int i = 0; i < fdsArraySize; ++i) {
                _fds_array[i] = defaultFd;
            }
        }

        ~SelectServer() {
            if (_listen_sockfd != defaultFd) {
                close(_listen_sockfd);
            }
            if (_fds_array != nullptr) {
                delete[] _fds_array;
                _fds_array = nullptr;
            }
        }

    public:
        // 初始化服务器
        void initServer() {
            _listen_sockfd = Socket::getSocket(); // 获得监听套接字
            Socket::bindSocket(_listen_sockfd, _port); // 绑定监听套接字
            Socket::listenSocket(_listen_sockfd); // 设置监听状态
            _fds_array[0] = _listen_sockfd; //* 将监听套接字设定进装有效fd的数组中，让select来接管accept的等待过程

            logMessage(NORMAL, "initServer success!");
        }

        void run() {
            for (;;) {
                fd_set read_fds; // 需要让select管理输入事件的等待过程的fd所在的位图
                FD_ZERO(&read_fds); // 初始化位图
                int max_fd = _fds_array[0]; // 将最大的fd值初始化

                for (int i = 0; i < fdsArraySize; ++i) {
                    if (_fds_array[i] != defaultFd) {
                        FD_SET(_fds_array[i], &read_fds);

                        max_fd = max_fd < _fds_array[i] ? _fds_array[i] : max_fd;
                    }
                }
                logMessage(NORMAL, "max fd is %d", max_fd);

                //! select的后四个参数都是输入输出型参数，在调用完函数后会改变。需要密切注意，不要被坑了
                //* n为select等待到的已经有输入事件的fd的个数
                int n = select(max_fd + 1 /* 用于遍历位图，+1是因为左闭右开[0, max_fd + 1) */, &read_fds, nullptr, nullptr, nullptr /* 阻塞时等待 */);
                if (n > 0) { // 成功等待
                    logMessage(NORMAL, "select wait success!");
                    handleReadEvents(read_fds);
                }
                else if (n == 0) { // 超时返回，处理非阻塞或计时阻塞select没等到输入事件的情况
                    logMessage(NORMAL, "time out...");
                }
                else { // n < 0, select出错了
                    logMessage(ERROR, "select error occur, code: %d, description: %s", errno, strerror(errno));
                }
            }
        }

    private:
        // 展示_fds_array中有效的fd
        void showFds() {
            std::cout << "_fds_array contains: ";
            for (int i = 0; i < fdsArraySize; ++i) {
                if (_fds_array[i] != defaultFd) {
                    std::cout << _fds_array[i] << " ";
                }
            }

            std::cout << std::endl;
        }

        /**
         * 获取一个新连接
         ** 这个函数是在select为listen套接字成功等待之后才会调用
         ** 此时系统接口accept不会通过阻塞来等待连接，而是直接获取一个新连接
        */
        void acceptLink() {
            std::string client_ip;
            uint16_t client_port;
            int sockfd = Socket::acceptSocket(_listen_sockfd, &client_ip, &client_port);

            if (sockfd < 0) { // 获取新连接失败
                logMessage(ERROR, "accept a new link err!");
                return;
            }

            logMessage(NORMAL, "accept a new link success sockfd: %d, client_ip: %s, client_port: %d", sockfd, client_ip.c_str(), client_port);

            /*
            * 我们在成功获取到新连接后，不能像以前一样直接recv或read，
            * 因为新连接不一定发送数据过来了，又因为该程序为但进程(线程)，所以就会阻塞在这里
            * 那么就和之前最普通的tcp服务器一样了
            * 所以我们要做的是将新获取的fd放进_fds_array中，让select替recv和read等待
            */

           //task 在_fds_array中找空位置，有的话就将sockfd放进去，没有的话就关闭连接
           int i = 0;
            for (; i < fdsArraySize; ++i) {
                if (_fds_array[i] == defaultFd) {
                    break;
                }
            }
            if (i == fdsArraySize) { // 没有空位置
                logMessage(WARNING, "server is crowded, please wait some time");
                close(sockfd);
                return;
            }
            else {
                _fds_array[i] = sockfd;
                logMessage(NORMAL, "load sockfd %d into _fds_array[%d] success!", sockfd, i);
                showFds();
            }
        }

        /**
         * 从有输入事件的套接字中获取消息
         * 
         * @param sockfd 有输入事件的套接字
         * @param pos sockfd在_fds_array中的下标, 便于从_fds_array找到并删除sockfd
        */
        void receiveMessage(const int& sockfd, const int& pos) {
            char buffer[receiveBufferSize] = {};
            ssize_t s = recv(sockfd, buffer, receiveBufferSize - 1, 0);
            if (s > 0) { // 成功读取到数据
                buffer[s] = '\0';
                logMessage(NORMAL, "receive message %s success!", buffer);
            }
            else if (s == 0) { // 读到文件结尾EOF(客户端关闭)
                resetSockfd(sockfd, pos);
                logMessage(NORMAL, "client %d quit", sockfd);
                return;
            }
            else {
                resetSockfd(sockfd, pos);
                logMessage(ERROR, "Unknown error occurred in receiveMessage: %s", strerror(errno));
                return;
            }

            //task 处理recv上来的数据
            std::string response(_handle_method(std::string(buffer)));
            logMessage(NORMAL, "handle data success, response: %s", response.c_str());

            //task 将处理完的数据发送回去
            send(sockfd, response.c_str(), response.size(), 0);
            logMessage(NORMAL, "send response to client success");
        }

        /**
         * 处理已经准备好的输入事件(可能存在多个)
         * 
         * @param read_fds 存储有输入事件的fd的位图
        */
        void handleReadEvents(const fd_set& read_fds) {
            for (int i = 0; i < fdsArraySize /* fdsArraySize也是select能够管理的sockfd的数量 */; ++i) {
                if (_fds_array[i] == defaultFd) {
                    continue; // 过滤掉非法的sockfd(不存在或不需要select管理)
                }

                if (FD_ISSET(_fds_array[i], &read_fds)) { // 保证_fds_array[i]在read_fds被设置(有输入事件)
                    if (_fds_array[i] == _listen_sockfd) {
                        // listen到了一个新连接
                        acceptLink();
                    }
                    else {
                        receiveMessage(_fds_array[i], i);
                    }
                }
            }

            logMessage(NORMAL, "handleReadEvents success!");
        }

        // 将sockfd关闭并将其从_fds_array中移除
        void resetSockfd(const int& sockfd, const int& pos) {
            close(sockfd);
            _fds_array[pos] = defaultFd; // 将sockfd从select需要关心的套接字中去除
        }

    }; //@end class SelectServer
} //@end select_server